package com.activiti.util;

import com.activiti.dto.ModelNewDto;
import com.activiti.dto.MyFormProperty;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.*;
import org.activiti.engine.form.FormProperty;
import org.activiti.engine.form.StartFormData;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: myspringboot
 * @description:
 * @author: zyj
 * @create: 2020-01-16 13:13
 **/
@Service
public class ActivitiUtilService {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private IdentityService identityService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private FormService formService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;
    /**
    * @Description: 根据模型id部署模型
    * @Param: [id]
    * @return: void
    * @Author: zyj
    * @Date: 2020/1/16
    */
    @Transactional(rollbackFor = Exception.class)
    public void deploy(String id) throws Exception {

        //获取模型
        Model modelData = repositoryService.getModel(id);
        byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());

        if (bytes == null) {
            throw new Exception("模型数据为空，请先设计流程并成功保存，再进行发布。");
        }

        JsonNode modelNode = new ObjectMapper().readTree(bytes);

        BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
        if(model.getProcesses().size()==0){
            throw new Exception("数据模型不符要求，请至少设计一条主线流程。");
        }
        byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);


        //发布流程
        String processName = modelData.getName() + ".bpmn20.xml";
        Deployment deployment = repositoryService.createDeployment()
                .name(modelData.getName())
                .category(modelData.getCategory())
                .addString(processName, new String(bpmnBytes, "UTF-8"))
                .deploy();
        modelData.setDeploymentId(deployment.getId());
        repositoryService.saveModel(modelData);
    }

    /**
    * @Description: 新建模型
    * @Param: [modelNewDto]
    * @return: String 模型ID
    * @Author: zyj
    * @Date: 2020/1/16
    */
    public String newModel(ModelNewDto modelNewDto) throws Exception{
        //初始化一个空模型
        Model model = repositoryService.newModel();

        //设置一些默认信息
        String name = modelNewDto.getName();
        String description = modelNewDto.getDescription();
        int revision = modelNewDto.getRevision();
        String key = modelNewDto.getKey();

        JSONObject modelNode = new JSONObject();
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);

        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());

        repositoryService.saveModel(model);
        String id = model.getId();

        //完善ModelEditorSource
        JSONObject editorNode = new JSONObject();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        JSONObject stencilSetNode = new JSONObject();
        stencilSetNode.put("namespace",
                "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.put("stencilset", stencilSetNode);
        repositoryService.addModelEditorSource(id,editorNode.toString().getBytes("utf-8"));

        return id;
    }

    /**
    * @Description: 根据部署id获取部署表单表单
    * @Param: [devolpementId, userId]
    * @return: java.util.Map<java.lang.String,java.lang.Object>
    * @Author: zyj
    * @Date: 2020/1/16
    */
    public Map<String, Object> getDevelopementForm(String devolpementId) throws Exception{
        ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(devolpementId).singleResult();
        Map<String, Object> map = getFormByDefId(pd.getId());

        return map;
    }


    /**
     * @Description: 根据部署id获取部署表单表单
     * @Param: [devolpementId, userId]
     * @return: java.util.Map<java.lang.String,java.lang.Object>
     * @Author: zyj
     * @Date: 2020/1/16
     */
    public Map<String, Object> getFormByDefId(String processDefinitionId) throws Exception{
       /**获取流程需要填入的表单*/
        String formId = formService.getStartFormKey(processDefinitionId);
        StartFormData startFormData = formService.getStartFormData(processDefinitionId);

        List<FormProperty> formPropertyList = startFormData.getFormProperties();
        String formKey = startFormData.getFormKey();

        Map<String, Object> map = new HashMap<>(2);

        List<MyFormProperty> myFormPropertyList = coverList(formPropertyList);
        map.put("properties", myFormPropertyList);
        map.put("formId", formId);
        map.put("formKey", formKey);

        return map;
    }

    /**
    * @Description: 表单数据转换
    * @Param: [formPropertyList]
    * @return: java.util.List<com.activiti.dto.MyFormProperty>
    * @Author: zyj
    * @Date: 2020/1/17
    */
    public List<MyFormProperty> coverList(List<FormProperty> formPropertyList){
        List<MyFormProperty> list = new ArrayList<>();

        for(FormProperty formProperty: formPropertyList){
            list.add(new MyFormProperty(formProperty));
        }

        return list;
    }

    /**
    * @Description:  开启流程
    * @Param: [formMap 表单, userId 发起人, processDefId 部署id]
    * @return: void
    * @Author: zyj
    * @Date: 2020/1/19
    */
    @Transactional(rollbackFor = Exception.class)
    public void startProcess(Map<String, Object> formMap,String userId,String processDefId) throws Exception{

        //设置流程启动人
        //必须在启动前设置
        identityService.setAuthenticatedUserId(userId);
        Authentication.setAuthenticatedUserId(userId);

        // 启动流程 activitiemployeeProcess
        ProcessInstance pi= runtimeService.startProcessInstanceById(processDefId, formMap);
        //流程实例启动后，流程会跳转到请假申请节点
        Task vacationApply = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        String taskId = vacationApply.getId();
        //设置请假申请任务的执行人
        taskService.setAssignee(taskId, userId);
        //设置流程全局变量
        taskService.setVariables(taskId, formMap);

        //启动流程后第一个节点自动完成
        taskService.complete(taskId);
    }

    /**
    * @Description: 根据任务id获取流程变量
    * @Param: [taskId]
    * @return: java.util.Map<java.lang.String,java.lang.Object>
    * @Author: zyj
    * @Date: 2020/1/21
    */
    public Map<String, Object> getVariablesByTaskId(String taskId){
        Map<String,Object> variables=taskService.getVariables(taskId);
        return variables;
    }

    /**
    * @Description: 根据用户名或者用户组名，获取用户任务列表
    * @return: void
    * @Author: zyj
    * @Date: 2020/1/19
    */
    public List<Task> userList(String userName,List<String> groups) throws Exception{

        TaskQuery taskQuery = taskService.createTaskQuery();
        if(null!=userName&&!userName.isEmpty()){
            taskQuery.taskAssignee(userName);
        }
        //获取任务列表
        if(null!=groups&&!groups.isEmpty()){
            taskQuery.or().taskCandidateGroupIn(groups);
        }
        List<Task> taskList = taskQuery.list();
        return taskList;
    }

    /**
    * @Description: 根据用户id获取历史数据
    * @Param: [usrId]
    * @return: java.util.List<org.activiti.engine.history.HistoricProcessInstance>
    * @Author: zyj
    * @Date: 2020/1/20
    */
    public List<HistoricProcessInstance> getHistorProcess(String usrId){
        List<HistoricProcessInstance> historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().
                startedBy(usrId).list();
        return historicProcessInstanceList;
    }

    /**
     * @Description: 根据流程id获取参数
     * @Param: [usrId]
     * @return: java.util.List<org.activiti.engine.history.HistoricProcessInstance>
     * @Author: zyj
     * @Date: 2020/1/20
     */
    public Map<String, Object> getVariblesByProcessId(String processInstanceId){
        List<HistoricVariableInstance> historicVariableInstances = historyService.createHistoricVariableInstanceQuery()
                .processInstanceId(processInstanceId).list();
        Map<String, Object> map = new HashMap<>();
        for(int i=0; i<historicVariableInstances.size(); i++){
            HistoricVariableInstance historicVariableInstance = historicVariableInstances.get(i);
            map.put(historicVariableInstance.getVariableName(),historicVariableInstance.getValue());
        }

        return map;
    }

    /**
    * @Description: 根据流程id获取activiti图片
    * @Param: [processInstanceId]
    * @return: java.io.InputStream
    * @Author: zyj
    * @Date: 2020/1/20
    */
    public InputStream getActivitiProccessImage(String processInstanceId) throws Exception {
        //  获取历史流程实例
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).singleResult();

        if (historicProcessInstance == null) {
            throw new Exception();
        } else {
            // 获取流程定义
            ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                    .getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId());

            // 获取流程历史中已执行节点，并按照节点在流程中执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(processInstanceId).orderByHistoricActivityInstanceId().asc().list();

            // 已执行的节点ID集合
            List<String> executedActivityIdList = new ArrayList<String>();
            @SuppressWarnings("unused")
            int index = 1;
            System.out.println("获取已经执行的节点ID");
            for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
                executedActivityIdList.add(activityInstance.getActivityId());
                System.out.println("第[" + index + "]个已执行节点=" + activityInstance.getActivityId() + " : " +activityInstance.getActivityName());
                index++;
            }

            // 获取流程图图像字符流
            InputStream imageStream = ProcessDiagramGenerator.generateDiagram(processDefinition, "png", executedActivityIdList);

            System.out.println("[完成]-获取流程图图像");
            return imageStream;
        }
    }

    /**
    * @Description: 完成审批
    * @Param: [userId, taskId, pizhu]
    * @return: void
    * @Author: zyj
    * @Date: 2020/1/21
    */
    public void complementTask(String userId, String taskId, String pizhu){
        // 使用任务id,获取任务对象，获取流程实例id
        Task task=taskService.createTaskQuery().taskId(taskId).singleResult();
        //利用任务对象，获取流程实例id
        String processInstancesId=task.getProcessInstanceId();
        // 添加批注时候的审核人，通常应该从session获取
        Authentication.setAuthenticatedUserId(userId);
        taskService.addComment(taskId, processInstancesId, pizhu);
        taskService.complete(taskId);
    }

}
